﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure;
using System.Data.Services.Client;
using AzureGames.Model;
using AzureGames.Data.Implementation.ServiceEntities;
using System.Diagnostics;

namespace AzureGames.Data.Implementation
{
    public abstract class AzureTableRepository<T> where T : BaseTableEntity, new()
    {
        protected readonly string tableName;
        protected readonly TableServiceContext dataContext;
        protected TraceSource Trace = new TraceSource("AzureGames");

        public AzureTableRepository(string tableName)
        {
            this.tableName = tableName;
            this.dataContext = GetClientForTable();
        }

        protected AzureResult Add(T item)
        {
            var sw = Stopwatch.StartNew();

            try
            {
                item.CreatedDate = DateTime.Now;
                item.LastUpdatedDate = DateTime.Now;

                dataContext.AddObject(this.tableName, item);
                dataContext.SaveChanges();
            }
            catch (Exception ex)
            {
                Trace.TraceEvent(TraceEventType.Error, GetEventId(RepositoryAction.Add),
                                "AzureTableRepository.Add(Table:{0} PartitionKey:{1} RowKey{2}) failed with:{3}\nInnerMessage:{4}\nStackTrace:{5}",
                                this.tableName,
                                item.PartitionKey,
                                item.RowKey,
                                ex.Message,
                                ex.InnerException == null ? "" : ex.InnerException.Message,
                                ex.StackTrace);

                throw ex;
            }
            finally
            {
                sw.Stop();
                Trace.TraceInformation("AzureTableRepository.Add(Table:{0} PartitionKey:{1} RowKey{2}) completed in {3} ticks.", this.tableName, item.PartitionKey, item.RowKey, sw.ElapsedTicks);
            }

            return new AzureResult
            {
                Code = 0
            };
        }

        

        protected AzureResult Update(T item)
        {
            var sw = Stopwatch.StartNew();

            try
            {
                item.LastUpdatedDate = DateTime.Now;

                dataContext.UpdateObject(item);
                dataContext.SaveChanges();
            }
            catch (Exception ex)
            {
                Trace.TraceEvent(TraceEventType.Error, GetEventId(RepositoryAction.Update),
                                "AzureTableRepository.Update(Table:{0} PartitionKey:{1} RowKey{2}) failed with:{3}\nInnerMessage:{4}\nStackTrace:{5}",
                                this.tableName,
                                item.PartitionKey,
                                item.RowKey,
                                ex.Message,
                                ex.InnerException == null ? "" : ex.InnerException.Message,
                                ex.StackTrace);

                throw ex;
            }
            finally
            {
                sw.Stop();
                Trace.TraceInformation("AzureTableRepository.Update(Table:{0} PartitionKey:{1} RowKey{2}) completed in {3} ticks.", this.tableName, item.PartitionKey, item.RowKey, sw.ElapsedTicks);
            }
            return new AzureResult
            {
                Code = 0
            };
        }



        protected AzureResult Delete(T item)
        {
            var sw = Stopwatch.StartNew();

            try
            {
                dataContext.DeleteObject(item);
                var response = dataContext.SaveChanges();
            }
            catch (Exception ex)
            {
                Trace.TraceEvent(TraceEventType.Error, GetEventId(RepositoryAction.Delete),
                                "AzureTableRepository.Delete(Table:{0} PartitionKey:{1} RowKey{2}) failed with:{3}\nInnerMessage:{4}\nStackTrace:{5}",
                                this.tableName,
                                item.PartitionKey,
                                item.RowKey,
                                ex.Message,
                                ex.InnerException == null ? "" : ex.InnerException.Message,
                                ex.StackTrace);

                throw ex;
            }
            finally
            {
                sw.Stop();
                Trace.TraceInformation("AzureTableRepository.Delete(Table:{0} PartitionKey:{1} RowKey{2}) completed in {3} ticks.", this.tableName, item.PartitionKey, item.RowKey, sw.ElapsedTicks);
            }

            // TODO: parse response

            return new AzureResult
            {
                Code = 0
            };
        }


        private TableServiceContext GetClientForTable()
        {
            var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
            var tableClient = account.CreateCloudTableClient();


            CreateTableIfNotExist(tableClient);
            var context = tableClient.GetDataServiceContext();

            //Need to do the IgnoreResourceNotFoundException to true otherwise there's an annoying problem
            //wasted an hour till I found Ivan's blog, thanks Ivan
            //http://ivanfioravanti.wordpress.com/2011/11/28/azure-storage-table-and-the-firstordefault-operator/
            context.IgnoreResourceNotFoundException = true;

            return context;
        }

        protected void CreateTableIfNotExist(CloudTableClient client)
        {
            client.CreateTableIfNotExist<T>(this.tableName);
        }

        protected List<T> Get(string partitionKey)
        {
            var sw = Stopwatch.StartNew();

            try
            {
                return dataContext.CreateQuery<T>(this.tableName)
                                   .Where(n => n.PartitionKey == partitionKey).ToList();
            }
            catch (Exception ex)
            {
                Trace.TraceEvent(TraceEventType.Error, GetEventId(RepositoryAction.Get),
                                "AzureTableRepository.Get(Table:{0} PartitionKey:{1}) failed with:{3}\nInnerMessage:{4}\nStackTrace:{5}",
                                this.tableName,
                                partitionKey,
                                ex.Message,
                                ex.InnerException == null ? "" : ex.InnerException.Message,
                                ex.StackTrace);

                throw ex;
            }
            finally
            {
                sw.Stop();
                Trace.TraceInformation("AzureTableRepository.Get(Table:{0} PartitionKey:{1}) completed in {2} ticks.", this.tableName, partitionKey, sw.ElapsedTicks);
            }
        }
        protected T Get(string partitionKey, string rowKey)
        {
            var sw = Stopwatch.StartNew();

            try
            {
                return dataContext.CreateQuery<T>(this.tableName)
                        .Where(n => n.PartitionKey == partitionKey && n.RowKey == rowKey)
                        .FirstOrDefault();
            }
            catch (Exception ex)
            {
                Trace.TraceEvent(TraceEventType.Error, GetEventId(RepositoryAction.Get),
                                "AzureTableRepository.Get(Table:{0} PartitionKey:{1} RowKey{2}) failed with:{3}\nInnerMessage:{4}\nStackTrace:{5}",
                                this.tableName,
                                partitionKey,
                                rowKey,
                                ex.Message,
                                ex.InnerException == null ? "" : ex.InnerException.Message,
                                ex.StackTrace);

                throw ex;
            }
            finally
            {
                sw.Stop();
                Trace.TraceInformation("AzureTableRepository.Get(Table:{0} PartitionKey:{1} RowKey{2}) completed in {3} ticks.", this.tableName, partitionKey, rowKey, sw.ElapsedTicks);
            }
        }


        enum RepositoryAction { Add=100, Delete=200, Update=300, Get=0 }; 
        private int GetEventId(RepositoryAction action)
        {
            switch (this.tableName)
            {
                case "AIPlayer":
                    return 101 + (int) action;
                case "Board":
                    return 102 + (int)action;
                case "BoardPlayer":
                    return 103 + (int)action;
                case "BoardProperty":
                    return 104 + (int)action;
                case "GameDefinition":
                    return 105 + (int)action;
                case "GameProperty":
                    return 106 + (int)action;
                case "GameResource":
                    return 107 + (int)action;
                case "GameStatistics":
                    return 108 + (int)action;
                case "User":
                    return 109 + (int)action;
                case "UserStatistics":
                    return 110 + (int)action;
                default:
                    return 100 + (int)action;
            }
        }
    }


}
